/**
 * Created on Apr 20, 2000
 */
package jvs.ftp.server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.StringTokenizer;

import x.java.io.File;

/**
 * @author qiangli
 * 
 */
public class PI implements Runnable {
	private Socket sock = null;

	private BufferedReader in = null;

	private BufferedWriter out = null;

	private String request = null;

	private String command = null;

	private String host;

	private int port;

	private String user = "anonymous";

	private String password = null;

	private char type = 'I'; // ASCII

	private char mode = 'S'; // Stream

	private String root = null;

	private File cwd = new File("jcr:/");

	//private String pathname = null;

	// Reply Codes
	private final static String CODE150 = "150 File status okay, about to open data connection.";

	private final static String CODE200 = "200 Command okay.";

	private final static String CODE202 = "202 Command not implemented, superfluous at this site.";

	private final static String CODE215 = "215 UNIX bogus.";

	private final static String CODE220 = "220 Service ready for new user. [Java FTP Server 1.0]";

	private final static String CODE221 = "221 Service closing control connection.";

	private final static String CODE226 = "226 Closing data connection.";

	private final static String CODE230 = "230 User logged in, proceed.";

	private final static String CODE250 = "250 Requested file action okay, completed.";

	private final static String CODE257 = "257 "; // "pathname" created

	private final static String CODE331 = "331 User name okay, need password.";

	private final static String CODE502 = "502 Command not implemented.";

	private final static String CODE504 = "504 Command not implemented for that parameter.";

	//private final static String CODE530 = "530 Not logged in.";

	private final static String CODE550 = "550 Requested action not taken.";

	public PI(Socket sock, String root) {
		this.sock = sock;
		try {
			this.root = new File(root).getCanonicalPath();
		} catch (IOException e) {
		}
		this.cwd = new File(root);
	}

	private void reply(String msg) {
		try {
			out.write(msg + "\r\n");
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void run() {
		try {
			in = new BufferedReader(
					new InputStreamReader(sock.getInputStream()));
			out = new BufferedWriter(new OutputStreamWriter(sock
					.getOutputStream()));
			InetAddress inet = sock.getInetAddress();
			//InetAddress localip = inet.getLocalHost();
			host = inet.getHostAddress();
			System.out.println("inet: " + host);

			reply(CODE220);

			while (true) {
				request = in.readLine() + " ";
				System.out.println("Request: " + request);
				command = request.substring(0, 4).trim().toUpperCase();
				System.out.println("Command: " + command);
				System.out.println("host: " + host);
				System.out.println("port: " + port);
				System.out.println("cwd: " + cwd);
				// Access Control Commands
				if (command.equals("USER")) {
					user();
				} else if (command.equals("PASS")) {
					pass();
				} else if (command.equals("ACCT")) {
					niok();
				} else if (command.equals("CWD")) {
					xcwd();
				} else if (command.equals("CDUP")) {
					cdup();
				} else if (command.equals("SMNT")) {
					ni();
				} else if (command.equals("REIN")) {
					ni();
				} else if (command.equals("QUIT")) {
					quit();
					return;
				} else
				// Transfer Parameter Commands
				if (command.equals("PORT")) {
					port();
				} else if (command.equals("PASV")) {
					ni();
				} else if (command.equals("TYPE")) {
					type();
				} else if (command.equals("STRU")) {
					ni();
				} else if (command.equals("MODE")) {
					mode();
				} else
				// FTP Service Commands
				if (command.equals("RETR")) {
					retr();
				} else if (command.equals("STOR")) {
					stor();
				} else if (command.equals("STOU")) {
					ni();
				} else if (command.equals("APPE")) {
					ni();
				} else if (command.equals("ALLO")) {
					ni();
				} else if (command.equals("REST")) {
					ni();
				} else if (command.equals("RNFR")) {
					ni();
				} else if (command.equals("RNTO")) {
					ni();
				} else if (command.equals("ABOR")) {
					ni();
				} else if (command.equals("DELE")) {
					dele();
				} else if (command.equals("RMD") || command.equals("XRMD")) {
					xrmd();
				} else if (command.equals("MKD") || command.equals("XMKD")) {
					xmkd();
				} else if (command.equals("PWD") || command.equals("XPWD")) {
					xpwd();
				} else if (command.equals("LIST")) {
					list();
				} else if (command.equals("NLST")) {
					nlst();
				} else if (command.equals("SITE")) {
					ni();
				} else if (command.equals("SYST")) {
					syst();
				} else if (command.equals("STAT")) {
					ni();
				} else if (command.equals("HELP")) {
					ni();
				} else if (command.equals("NOOP")) {
					ni();
				} else {
					ni();
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void niok() {
		reply(CODE202);
	}

	private void ni() {
		reply(CODE502);
	}

	//
	private void user() {
		user = request.substring(4).trim();
		reply(CODE331);
	}

	private void pass() {
		password = request.substring(4).trim();
		// TODO authentication
		reply(CODE230);
	}

	// acct

	private void xcwd() {
		String sub = request.substring(4).trim();
		File f = sub.startsWith("/") ? new File(root, sub) : new File(cwd, sub);
		String path = null;
		try {
			path = f.getCanonicalPath();
		} catch (IOException e) {
			e.printStackTrace();
		}

		if (path.startsWith(root) && f.isDirectory()) {
			cwd = new File(path);
			reply(CODE250 + " [" + vpath(cwd) + "]");
		} else {
			reply(CODE550);
		}
	}

	private void cdup() {
		cwd = (File)cwd.getParentFile();
		reply(CODE250 + " [" + vpath(cwd) + "]");
	}

	// smnt
	// rein

	private void quit() {
		reply(CODE221);
		try {
			in.close();
			out.close();
			sock.close();
		} catch (IOException e) {
		}
	}

	private void port() {
		StringTokenizer st = new StringTokenizer(request.substring(4), ",");
		String[] token = new String[6];
		int i = 0;
		while (st.hasMoreElements()) {
			token[i++] = st.nextToken().trim();
		}
		host = token[0] + "." + token[1] + "." + token[2] + "." + token[3];
		port = Integer.parseInt(token[4]) * 16 * 16
				+ Integer.parseInt(token[5]);
		System.out.println("host: [" + host + "] port: [" + port + "]");
		reply(CODE200);
	}

	// pasv

	private void type() {
		char t = request.substring(4).trim().toUpperCase().charAt(0);
		if (t == 'I' || t == 'A') {
			type = t;
			reply(CODE200);
		} else {
			reply(CODE504);
		}
	}

	// stru

	private void mode() {
		char m = request.substring(4).trim().toUpperCase().charAt(0);
		if (m == 'S') {
			mode = m;
			reply(CODE200);
		} else {
			reply(CODE504);
		}
	}

	private void retr() {
		reply(CODE150);
		String sub = request.substring(4).trim();
		File f = sub.startsWith("/") ? new File(root, sub) : new File(cwd, sub);

		try {
			//RandomAccessFile file = new RandomAccessFile(f, "r");
			InputStream file = f.toURL().openStream();
			Socket t = new Socket(host, port);
			OutputStream out2 = t.getOutputStream();
			byte bb[] = new byte[1024];
			int amount;
			while ((amount = file.read(bb)) != -1) {
				out2.write(bb, 0, amount);
			}
			out2.close();
			reply(CODE226);
			file.close();
			t.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void stor() {
		reply(CODE150);
		String sub = request.substring(4).trim();
		File f = sub.startsWith("/") ? new File(root, sub) : new File(cwd, sub);
		try {
			//RandomAccessFile file = new RandomAccessFile(f, "rw");
			if (!f.exists()) {
				f.createNewFile();
			}
			OutputStream file = f.toURL().openConnection().getOutputStream();
			Socket t = new Socket(host, port);
			InputStream in2 = t.getInputStream();
			byte bb[] = new byte[1024];
			int amount;

			while ((amount = in2.read(bb)) != -1) {
				file.write(bb, 0, amount);
			}
			in2.close();
			reply(CODE226);
			file.close();
			t.close();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// stou
	// appe
	// allo
	// rest
	// rnfr
	// rnto
	// abor

	private void dele() {
		String str = request.substring(4).trim();
		File f = new File(cwd, str);
		boolean rc = f.delete();
		if (rc) {
			reply(CODE250);
		} else {
			reply(CODE550);
		}
	}

	private void xrmd() {
		String str = request.substring(4).trim();
		File f = new File(cwd, str);
		boolean rc = f.delete();
		if (rc) {
			reply(CODE250);
		} else {
			reply(CODE550);
		}
	}

	private void xmkd() {
		String str = request.substring(4).trim();
		File f = new File(cwd, str);
		boolean rc = f.mkdir();
		if (rc) {
			reply(CODE257 + "\"" + vpath(f) + "\" created.");
		} else {
			reply(CODE550);
		}
	}

	private void xpwd() {
		reply(CODE257 + "\"" + vpath(cwd) + "\" is current directory.");
	}

	private void list() {
		reply(CODE150);
		try {
			Socket s = new Socket(host, port);
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s
					.getOutputStream()));
			java.io.File[] fl = cwd.listFiles();
			for (int i = 0; i < fl.length; i++) {
				SimpleDateFormat df = new SimpleDateFormat("MMM dd yyyy");
				String date = df.format(new Date(fl[i].lastModified()));
				// unix like output
				bw.write((fl[i].isDirectory() ? "d" : "-")
						+ (fl[i].canRead() ? "r" : "-")
						+ (fl[i].canWrite() ? "w" : "-") + "------");
				bw.write("\t1");
				bw.write("\t" + user);
				bw.write("\t" + user);
				bw.write("\t" + fl[i].length());
				bw.write("\t" + date);
				bw.write("\t" + fl[i].getName());
				bw.write("\r\n");
			}
			bw.close();
			s.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		reply(CODE226);
	}

	private void nlst() {
		reply(CODE150);
		try {
			Socket s = new Socket(host, port);
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s
					.getOutputStream()));
			java.io.File[] fl = cwd.listFiles();
			for (int i = 0; i < fl.length; i++) {
				bw.write(fl[i].getName() + "\r\n");
			}
			bw.close();
			s.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		reply(CODE226);
	}

	// site

	private void syst() {
		reply(CODE215);
	}

	// stat
	// help
	// noop

	private String vpath(File file) {
		String path = null;
		try {
			path = file.getCanonicalPath();
		} catch (IOException e) {
			e.printStackTrace();
		}
		if (path.equals(root)) {
			return "/";
		}
		String vp = path.substring(root.length()).replaceAll("\\\\", "/");
		if (vp.length() == 0) {
			return "/";
		}
		return vp;
	}
}
